import axios, {
  AxiosError,
  AxiosInstance,
  AxiosRequestConfig,
  AxiosResponse,
  Canceler,
} from "axios";

import { InitRequestConfig } from "./types";

class Request {
  instace: AxiosInstance;

  /***
   * 存放取消方法的映射
   */
  cancelRequestSourceMap: Map<String, Canceler> = new Map();

  constructor(config: InitRequestConfig) {
    this.instace = axios.create(config);

    const interceptors = config?.interceptors;

    this.instace.interceptors.request.use(
      interceptors?.requestInterceptor,
      interceptors?.requestInterceptorCatch
    );

    this.instace.interceptors.response.use(
      interceptors?.responseInterceptor,
      interceptors?.responseInterceptorCatch
    );
  }

  /***
   * 生成每个请求的唯一key
   */
  private getPendingKey(config: AxiosRequestConfig): string {
    const { url, method, params, data } = config;
    let requestData: Record<string, any> = {};
    if (typeof data === "string") {
      requestData = JSON.parse(data);
    } else {
      requestData = data;
    }
    const key = [
      url,
      method,
      JSON.stringify(params),
      JSON.stringify(requestData),
    ].join("&");

    return key;
  }

  /***
   * 存储每个请求的唯一cancel回调
   */
  private addPending(config: AxiosRequestConfig): void {
    const pendingKey = this.getPendingKey(config);
    config.cancelToken =
      config.cancelToken ||
      new axios.CancelToken((cancel) => {
        if (!this.cancelRequestSourceMap.has(pendingKey)) {
          this.cancelRequestSourceMap.set(pendingKey, cancel);
        }
      });
  }

  /***
   * 删除重复请求
   */
  private removePending(config: AxiosRequestConfig): void {
    const pendingKey = this.getPendingKey(config);
    if (this.cancelRequestSourceMap.has(pendingKey)) {
      const cancelToken = this.cancelRequestSourceMap!.get(pendingKey);
      cancelToken!(pendingKey);
      this.cancelRequestSourceMap.delete(pendingKey);
    }
  }

  /***
   * 取消全部请求
   */
  cancelAllRequest() {}

  request<T>(config: AxiosRequestConfig): Promise<T> {
    // 先删除重复请求
    this.removePending(config);
    this.addPending(config);

    return new Promise((resolve, reject) => {
      this.instace
        .request(config)
        .then((res: AxiosResponse<T>) => {
          this.removePending(res.config);
          resolve(res.data);
        })
        .catch((err: AxiosError) => {
          if (err?.config) {
            this.removePending(err.config);
          }
          reject(err);
        });
    });
  }
}

export default Request;
